using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using FederatedScaleOutDatabases.DomainModel;
using FederatedScaleOutDatabases.Interfaces;
using FederatedScaleOutDatabases.Strategies;

namespace FederatedScaleOutDatabases.Repositories
{
    public class FederatedRepository<T> : IRepository<T> where T : class
    {
        private readonly FederationStrategy<T> _federationStrategy;
        private readonly IList<FederatedDatabase> _federatedDatabases;
        private readonly Dictionary<FederatedDatabase, IRepository<T>> _databaseContextMapping;
        private bool _isDisposed;

        public FederatedRepository(FederationStrategy<T> federationStrategy, IList<FederatedDatabase> federatedDatabases)
        {
            _federationStrategy = federationStrategy;
            _federatedDatabases = federatedDatabases;
            _databaseContextMapping = new Dictionary<FederatedDatabase, IRepository<T>>();
        }



        public void Add(T entity)
        {
            FederatedDatabase federatedDatabase = null;

            if (_federationStrategy.FederationResolutionStrategy != null)
                federatedDatabase = _federationStrategy.FederationResolutionStrategy.GetFederatedDatabase(entity).FirstOrDefault();

            else if (_federationStrategy.FederateOnInfo != null && _federationStrategy.FederateOnInfo.Count > 0)
            {
                Expression expressionToEvaluate;
                if (_federationStrategy.FederateOnInfo.TryGetValue(entity.GetType(), out expressionToEvaluate))
                {
                    if (expressionToEvaluate is Expression<Func<T, string>>)
                    {
                        var func = (expressionToEvaluate as Expression<Func<T, string>>).Compile();

                        federatedDatabase = _federatedDatabases.FirstOrDefault(x => x.DatabaseName == func.Invoke(entity));
                    }
                    else
                        throw new InvalidCastException("Expression must be of type: " + typeof(Expression<Func<T, string>>));
                }
            }


            if (federatedDatabase != null)
            {
                IRepository<T> repository;
                if (!_databaseContextMapping.TryGetValue(federatedDatabase, out repository))
                {
                    var dbContext = new FedDbContext(federatedDatabase.ConnectionString);
                    repository = new Repository<T>(dbContext);
                    _databaseContextMapping.Add(federatedDatabase, repository);
                }

                repository.Add(entity);
            }
            else
                throw new InvalidOperationException("Federated Database instance is null");


        }

        public IEnumerable<T> Get(Expression<Func<T, bool>> condition)
        {
            IEnumerable<FederatedDatabase> federatedDatabasesForQuerying = null;
            var result = new List<IEnumerable<T>>();

            if (_federationStrategy.FederationResolutionStrategy != null)
                federatedDatabasesForQuerying = _federationStrategy.FederationResolutionStrategy.GetFederatedDatabases(condition);
            else if (_federationStrategy.FederateOnInfo != null && _federationStrategy.FederateOnInfo.Count > 0)
            {
                Expression expressionToEvaluate;
                if (_federationStrategy.FederateOnInfo.TryGetValue(typeof(T), out expressionToEvaluate))
                {
                    if (expressionToEvaluate is Expression<Func<T, string>>)
                    {
                        //TODO: ExpressionEquivalenceComparer to be plugged to find potential databases in federation.  If not found, then, fall back to all databases.

                        federatedDatabasesForQuerying = _federatedDatabases;
                    }
                    else
                        throw new InvalidCastException("Expression must be of type: " + typeof(Expression<Func<T, string>>).ToString());
                }
            }

            if (federatedDatabasesForQuerying != null)
            {
                var databasesForQuerying = federatedDatabasesForQuerying as FederatedDatabase[] ?? federatedDatabasesForQuerying.ToArray();
                var queryGetTasks = new Task<IEnumerable<T>>[databasesForQuerying.Count()];
                int counter = 0;
                foreach (var database in databasesForQuerying)
                {
                    IRepository<T> repository = null;
                    if (!_databaseContextMapping.TryGetValue(database, out repository))
                    {
                        var dbContext = new FedDbContext(database.ConnectionString);
                        repository = new Repository<T>(dbContext);
                        _databaseContextMapping.Add(database, repository);
                    }

                    //waiting for EF 6 to be released for ASYNC & AWAIT support :-)
                    //var task = new Task<IEnumerable<T>>(obj =>
                    //                                        {
                    //                                            Tuple<IRepository<T>, Expression<Func<T, bool>>> repositoryNPredicateTuple = obj as Tuple<IRepository<T>, Expression<Func<T, bool>>>;
                    //                                            return repositoryNPredicateTuple.Item1.Get(repositoryNPredicateTuple.Item2);
                    //                                        },
                    //                                    new Tuple<IRepository<T>, Expression<Func<T, bool>>>(repository, condition)); //thread state

                    //queryGetTasks[counter++] = task;

                    //task.Start();

                    queryGetTasks[counter++] = Task<IEnumerable<T>>.Factory.StartNew(threadStateTuple =>
                                                                                        {
                                                                                            var tupleState = threadStateTuple as Tuple<IRepository<T>, Expression<Func<T, bool>>>;
                                                                                            if (tupleState != null)
                                                                                            {
                                                                                                if (tupleState.Item1 != null)
                                                                                                {
                                                                                                    var repositoryInstance = tupleState.Item1;
                                                                                                    if (tupleState.Item2 != null)
                                                                                                    {
                                                                                                        var queryPredicate = tupleState.Item2;

                                                                                                        return repositoryInstance.Get(queryPredicate);
                                                                                                    }
                                                                                                }
                                                                                            }

                                                                                            throw new ArgumentNullException("threadStateTuple", "Thread state is null");
                                                                                        },
                                                                                        new Tuple<IRepository<T>, Expression<Func<T, bool>>>(repository, condition));//thread-state

                }

                Task.WaitAll(queryGetTasks.ToArray());
                
                //var rs = Task.Factory.ContinueWhenAll<IEnumerable<T>, IEnumerable<T>>(queryGetTasks.ToArray(), tasks => (from t in tasks select t.Result).SelectMany(r => r));
                //return rs.Result;

                queryGetTasks.ToList().ForEach(query => result.Add(query.Result));
            }

            return result.SelectMany(t => t);
            //return null;
        }

        public void Update(T entity)
        {
            throw new NotImplementedException();
        }

        public void Delete(T entity)
        {
            throw new NotImplementedException();
        }

        public void SaveChanges()
        {
            foreach (var repository in _databaseContextMapping.Values)
            {
                repository.SaveChanges();
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    foreach (var repository in _databaseContextMapping.Values)
                    {
                        repository.Dispose();
                    }
                }
            }

            _isDisposed = true;
        }
    }
}
